using System;
using System.IO;
using System.Text;

namespace NguyenHoa.Web.App_Code
{
  /// <summary>
  /// Class that makes it easier to read SWF (Flash) files
  /// Written by Michael Swanson (http://blogs.msdn.com/mswanson)
  /// </summary>
  public class SWFReader
  { 
    static private UInt32[] bitValues;              // For pre-computed bit values
    static private Single[] powers;                 // For pre-computed fixed-point powers
    private Stream stream = null;                   // Input stream from SWF data source
    private byte bitPosition = 0;                   // Current bit position within byte (only used for reading bit fields)
    private byte currentByte = 0;                   // Value of the current byte (only used for reading bit fields)

    #region Properties

    public Stream Stream
    {
      get { return stream; }
      set { stream = value; }
    }

    #endregion

    #region Constructors

    static SWFReader()
    {
      // Setup bit values for later lookup
      SWFReader.bitValues = new UInt32[32];
      for (byte power = 0; power < 32; power++)
      {
        SWFReader.bitValues[power] = (UInt32)(1 << power);
      }

      // Setup power values for later lookup
      SWFReader.powers = new Single[32];
      for (byte power = 0; power < 32; power++)
      {
        SWFReader.powers[power] = (Single)Math.Pow(2, power - 16);
      }
    }

    public SWFReader(Stream stream)
    {
      this.Stream = stream;
    }

    #endregion

    #region Stream manipulation

    public byte ReadByte()
    {
      int byteRead = this.stream.ReadByte();

      this.bitPosition = 8;           // So that ReadBit() knows that we've "used" this byte already

      if (byteRead == -1)
      {
        throw new ApplicationException("Attempted to read past end of stream");
      }

      return (byte)byteRead;
    }

    public bool ReadBit()
    {
      bool result;
      // Do we need another byte?
      if (this.bitPosition > 7)
      {
        this.currentByte = ReadByte();
        this.bitPosition = 0;       // Reset, since we haven't "used" this byte yet
      }
      // Read the current bit
      result = ((this.currentByte & SWFReader.bitValues[(7 - bitPosition)]) != 0);
      // Move to the next bit
      this.bitPosition++;
      return result;
    }

    #endregion

    #region Byte-aligned types (SI8, SI16, SI32, UI8, UI16, UI32, FIXED, STRING)

    // Read an unsigned 8-bit integer
    public byte ReadUI8()
    {
      return ReadByte();
    }

    // Read an array of unsigned 8-bit integers
    public byte[] ReadUI8(int n)
    {
      byte[] result = new byte[n];

      for (int index = 0; index < n; index++)
      {
        result[index] = ReadUI8();
      }

      return result;
    }

    // Read a signed byte
    public sbyte ReadSI8()
    {
      return (sbyte)ReadByte();
    }

    // Read an unsigned 16-bit integer
    public UInt16 ReadUI16()
    {
      UInt16 result = 0;

      result |= (UInt16)ReadByte();
      result |= (UInt16)(ReadByte() << 8);

      return result;
    }

    // Read a signed 16-bit integer
    public Int16 ReadSI16()
    {
      return (Int16)ReadUI16();
    }

    // Read an unsigned 32-bit integer
    public UInt32 ReadUI32()
    {
      UInt32 result = 0;

      result |= (UInt32)ReadByte();
      result |= (UInt32)(ReadByte() << 8);
      result |= (UInt32)(ReadByte() << 16);
      result |= (UInt32)(ReadByte() << 24);

      return result;
    }

    // Read a signed 32-bit integer
    public Int32 ReadSI32()
    {
      return (Int32)ReadUI32();
    }

    // Read a 32-bit 16.16 fixed-point number
    public Single ReadFIXED()
    {
      Single result = 0;

      result += (Single)(ReadByte() * SWFReader.powers[0]);
      result += (Single)(ReadByte() * SWFReader.powers[7]);
      result += (Single)(ReadByte() * SWFReader.powers[15]);
      result += (Single)(ReadByte() * SWFReader.powers[31]);

      return result;
    }

    // Read a string
    // TODO: Is StringBuilder worth it for these small strings?
    public string ReadSTRING()
    {
      string result = string.Empty;
      byte[] character = { 0x00 };

      // Grab characters until we hit 0x00
      do
      {
        character[0] = ReadByte();
        if (character[0] != 0x00)
        {
          result += Encoding.ASCII.GetString(character);
        }
      } while (character[0] != 0x00);

      return result;
    }

    #endregion

    #region Non-byte-aligned bit types (SB[nBits], UB[nBits], FB[nBits])

    // Read an unsigned bit value
    public UInt32 ReadUB(int nBits)
    {
      UInt32 result = 0;

      // Is there anything to read?
      if (nBits > 0)
      {
        // Calculate value
        for (int index = nBits - 1; index > -1; index--)
        {
          if (ReadBit())
          {
            result |= SWFReader.bitValues[index];
          }
        }
      }

      return result;
    }

    // Read a signed bit value
    public Int32 ReadSB(int nBits)
    {
      Int32 result = 0;

      // Is there anything to read?
      if (nBits > 0)
      {
        // Is this a negative number (MSB will be set)?
        if (ReadBit())
        {
          result -= (Int32)SWFReader.bitValues[nBits - 1];
        }

        // Calculate rest of value
        for (int index = nBits - 2; index > -1; index--)
        {
          if (ReadBit())
          {
            result |= (Int32)SWFReader.bitValues[index];
          }
        }
      }

      return result;
    }

    // Read a signed fixed-point bit value
    // TODO: Math.Pow probably isn't the fastest method of accomplishing this
    public Single ReadFB(int nBits)
    {
      Single result = 0;

      // Is there anything to read?
      if (nBits > 0)
      {
        // Is this a negative number (MSB will be set)?
        if (ReadBit())
        {
          result -= SWFReader.powers[nBits - 1];

        }

        // Calculate rest of value
        for (int index = nBits - 1; index > 0; index--)
        {
          if (ReadBit())
          {
            result += SWFReader.powers[index - 1];
          }
        }
      }

      return result;
    }

    #endregion
  }
}